from pickletools import uint8
import cv2
import numpy as np
from numba import uint8
from numpy.ma.core import resize
from openpyxl.styles.builtins import output
from skimage.io import imread

def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

# 参数1：需要排序的轮廓， 参数2：需要排序的方式，例如：left-to-right 从左到右 ，这个函数只能对原始的轮廓信息进行处理，如果是自己把想要的轮廓信息取出来组合在一起，不能用这个函数进行处理
def sort_contours(cnts, method='left-to-right'):
    # reverse=True 从大到小
    reverse = False  # 从小到大
    # 表示用第一个参数 x坐标值进行排序
    i = 0
    if method == 'right-to-left' or method == 'bottom-to-top':
        reverse = True
    if method == 'top-to-bottom' or method == 'bottom-to-top':
        # 表示用第二个参数 y坐标进行排序
        i = 1
    # cv2.boundingRect(c) 获取轮廓左上角的坐标及高宽值
    bounding_boxes = [cv2.boundingRect(c) for c in cnts]  # 用一个最小的矩形, 把找到的形状包起来(x, y, h, w)
    # zip将数据打包成元组形式, 例如:a[1],b[1]为一组    zip(*) * 默认将文件解压成列表
    # key=lambda b: b[1][i] b[1]表示为对第二个参数即bounding_boxes进行排序 ；b[1][i] 表示对bounding_boxes中的第i个数据进行排序(0对x， 1对y)
    (cnts, bounding_boxes) = zip(*sorted(zip(cnts, bounding_boxes), key=lambda b: b[1][i], reverse=reverse))
    return cnts, bounding_boxes


#读取数字模板
img = imread('C:/Users/nic/Desktop/opencv/picture/card/number2.png')
#转换为灰度图
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#二值化处理
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
#轮廓检测,只需要外轮廓即可
contours,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
#绘制轮廓
cv2.drawContours(img,contours,-1,(0,0,255),3)
#可以用注释掉的也可以用函数
#进行排序
sorted_contours = sort_contours(contours, method='left-to-right')[0]
"""
#计算每个轮廓的最大外接矩形
boundingBox = [cv2.boundingRect(c) for c in contours]
#用zip将轮廓信息和外接矩形绑定在一起
conbined = list(zip(boundingBox, contours))
#用x坐标值进行排序
sorted_combined = sorted(conbined,key=lambda item:item[0][0])
# 解压排序后的结果
sorted_boxes, sorted_contours = zip(*sorted_combined)
"""
# 创建字典digits，存储排序后的数字区域
template_digits = {}
#将排好序的数字找出感兴趣的范围，也就是把每个数字单独抠出来
for (idx, c) in enumerate(sorted_contours):
    # 重新计算外接矩形
    (x, y, w, h) = cv2.boundingRect(c)
    # 取出每个数字区域，roi表示感兴趣的区域region of interest）
    template_roi = ref[y:y + h, x: x + w]
    # resize成合适的大小
    template_roi = cv2.resize(template_roi, (57, 88))
    template_digits[idx] = template_roi
    # 逐个显示roi，其结果应该是数字从0到9
#    cv_show('template_roi', template_roi)

#对输入图形进行处理
#读取图形
image = imread('C:/Users/nic/Desktop/opencv/picture/card/bankcard1.png')
#转化为灰度图
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
#礼帽
tophat = cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)
# 使用sobel算子计算x轴方向梯度
gradX = cv2.Sobel(tophat,ddepth=cv2.CV_32F,dx=1,dy=0,ksize=-1)
# 使用绝对值得到梯度幅值
gradX = np.absolute(gradX)
# 归一化使得所有梯度值统一到 0 到 255 的范围内
(minVal,maxVal) = (np.min(gradX),np.max(gradX))
gradX = (255*((gradX-minVal)/(maxVal-minVal)))
# 修改一下数据类型
gradX = gradX.astype("uint8")
#cv_show('img',gradX)
#通过闭操作（先膨胀再腐蚀）将数字连在一起
gradX = cv2.morphologyEx(gradX , cv2.MORPH_CLOSE,sqKernel)
#cv_show('img',gradX)
#THRESH_OTSU方法可以在双峰中找到合适的阈值，将画面中的双主体分开
thresh = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
#再来一个闭操作，使得中间没有空隔链接起来
sqKernel1 = cv2.getStructuringElement(cv2.MORPH_RECT,(15,15))
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel1)
#cv_show('img',thresh)
#计算轮廓
contours,hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#画出轮廓
cnts = contours
cur_image = image.copy()
cv2.drawContours(cur_image,cnts,-1,(0,0,255),3)
#cv_show('img',cur_image)
#得到的轮廓不太规则，并且有不想要的轮廓
#locs保存想要的轮廓
locs = []
img2 = image.copy()
#遍历想要的轮廓
for (i,c) in enumerate(cnts):
    #用矩形逼近轮廓，获取矩形的坐标，长宽
    (x,y,w,h) = cv2.boundingRect(c)
    #算比例
    ar = w/float(h)

    #选择合适的区域，根据实际情况来
    if ar > 3 and ar < 4 :

        if(w>75 and w<90)and(h>20 and h<25):
            #把符合的留下来
            locs.append((x,y,w,h))
            #在想要的数字区域画出矩形
            img2 = cv2.rectangle(img2, (x, y), (x + w, y + h), (0, 255, 0), 2)

#cv_show('img', img2)
#将符合的轮廓从左到右进行排序
locs = sorted(locs,key=lambda x:x[0])

output = []
#遍历每个轮廓当中的数字
for (i,(gX,gY,gW,gH)) in enumerate(locs):
    groupOutput = []
    #截取部分区域
    group = gray[gY-5:gY+gH+5 , gX-5:gX+gW+5]
    #cv_show('img',group)
    #预处理二值化
    group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    #cv_show('img',group)
    #计算每一组的轮廓
    digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    #给轮廓进行排序
    digitCnts = sort_contours(digitCnts,method='left-to-right')[0]

    for c in digitCnts:
        #得到当前的轮廓，resize成合适的大小(与之前设定的每个数字大小相同)
        (x,y,w,h) = cv2.boundingRect(c)
        roi = group[y:y+h,x:x+w]
        roi = cv2.resize(roi,(57,88))
        #cv_show('roi',roi)
        #计算匹配得分
        scores = []
        for (digit,digitROI) in template_digits.items():
            result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)
            (_,score,_,_) = cv2.minMaxLoc(result)
            scores.append(score)
        #找到最大的得分，也就是找到最匹配的
        #这里的np.argmax取出的是最大数值所对应的索引
        groupOutput.append(str(np.argmax(scores)))

    #画出来
    cv2.rectangle(image,(gX-5,gY-5),(gX+gW+5,gY+gH+5),(0,0,255),1)
    cv2.putText(image,"".join(groupOutput),(gX,gY-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)
    output.extend(groupOutput)

cv_show('IMG',image)

